#include <buola/gui.h>

#include <buola/gui/cmenu.h>
#include <buola/gui/cicon.h>
#include <buola/gui/dnd/cdrag.h>
#include <buola/gui/ctoolkit.h>
#include <buola/gui/dnd/cdrop.h>
#include <buola/image/cgc.h>
#include <buola/utility/cregion.h>
#include <buola/gui/cconfig.h>
#include <buola/gui/cclipboard.h>

#include <buola/widgets/cmenubar.h>
#include <buola/widgets/clistbox.h>
#include <buola/widgets/clbrenderer.h>
#include <buola/widgets/cscrollbar.h>
#include <buola/widgets/ceditbox.h>
#include <buola/widgets/ctheme.h>

#include "clbarrayrenderer.h"
#include "clblistrenderer.h"
#include "clbdetailrenderer.h"
#include "clbcheckrenderer.h"
#include "clbgrouprenderer.h"
#include "clbstriprenderer.h"

namespace buola { namespace gui {

CListBox::CListBox()
    :   mRenderer(nullptr)
    ,   mScrollPos(0,0)
{
    SetStyle(STYLE_DEFAULTVIEW);

    mDragging=false;
    mSelecting=false;
    mEditing=nullptr;
    mEditBox=nullptr;

    mIconSize=CIcon::MEDIUM;
    mImageSize=CSize_i(mIconSize);

    mAutoUpdate=true;

    mMargins={0,0,0,0};

    SetFocusMode(EFocusMode::MOUSE|EFocusMode::TAB);
    SetBackColor({1.0,1.0,1.0});

    SetPointer(CPointer::STOCK_DEFAULT);

    if(!mFont) mFont=img::CFont::GetStock(L"control");

    mHScroll=new CScrollBar();
    mHScroll->eChanged.Connect(&CListBox::OnHScroll,this,_1);
    mHScroll->SetStyle(CScrollBar::STYLE_HORIZONTAL);
    mHScroll->SetRange(0,15);
    mHScroll->SetStep(2,10);
    mHScroll->SetValue(0,false);

    mVScroll=new CScrollBar();
    mVScroll->eChanged.Connect(&CListBox::OnVScroll,this,_1);
    mVScroll->SetRange(0,15);
    mVScroll->SetStep(2,10);
    mVScroll->SetValue(0,false);

    Attach(mHScroll,mVScroll);

    SetDragWindow(this);
    AddDropWindow(this);
}

CListBox::CListBox(CListWidget *pList)
    :   CListBox()
{
    LWCopyList(pList);
}

CListBox::~CListBox()
{
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
////////////////////////// Interface //////////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CListBox::EnsureVisible(SItem *pItem,bool pMore)
{
    CRect_d lRect(mRenderer->GetItemRect(pItem));

    CPoint_d lScrollBy(0,0);

    CSize_d lMargin(Size().x-lRect.w(),Size().y-lRect.h());

    if(lMargin.x<0||!pMore) lMargin.x=0;
    if(lMargin.y<0||!pMore) lMargin.y=0;

    if(lRect.l<0)
    {
        lScrollBy.x-=(-lRect.l)+lMargin.x*2/3;
    }
    else if(lRect.r>Size().x)
    {
        lScrollBy.x+=(lRect.r-Size().x)+lMargin.x*2/3;
    }
    if(lRect.t<0)
    {
        lScrollBy.y-=(-lRect.t)+lMargin.y*2/3;
    }
    else if(lRect.b>Size().y)
    {
        lScrollBy.y+=(lRect.b-Size().y)+lMargin.y*2/3;
    }

    if(lScrollBy.x==0&&lScrollBy.y==0) return;

    CPoint_d lNewPos=mScrollPos+lScrollBy;

    mHScroll->SetValue((int)lNewPos.x);
    mVScroll->SetValue((int)lNewPos.y);
}

void CListBox::SetFont(img::PFont pFont)
{
    mFont=pFont;
    if(mRenderer)
        mRenderer->OnBigChange();
}

void CListBox::SetIconSize(int pSize)
{
    if(pSize!=mIconSize)
    {
        mIconSize=pSize;

        if(mRenderer)
            mRenderer->OnBigChange();
    }
}

void CListBox::SetImageSize(const CSize_i &pSize)
{
    if(pSize!=mImageSize)
    {
        mImageSize=pSize;

        if(mRenderer)
            mRenderer->OnBigChange();
    }
}

void CListBox::SetAutoUpdate(bool pAutoUpdate)
{
    mAutoUpdate=pAutoUpdate;
}

void CListBox::Update()
{
    if(mRenderer)
        mRenderer->OnBigChange();
}

void CListBox::SetMargins(const CRect_d &pMargins)
{
    mMargins=pMargins;
    Update();
}

CSize_d CListBox::GetMaxImageSize()
{
    return mRenderer->GetMaxImageSize();
}

void CListBox::StartEditing(SItem *pItem)
{
    EnsureVisible(pItem,false);
    mEditBox=mRenderer->Edit(pItem);
    mEditBox->sEndEditor.Connect(&CListBox::EndEditing,this);
    mEditing=pItem;
}

void CListBox::EndEditing()
{
    if(!mEditing) return;
    mEditing->SetText(mEditBox->GetCaption());
    eEdited(mEditing,mEditing->mID);
    mEditBox->Unmap();
    mEditing=nullptr;
}

void CListBox::CreateRenderer()
{
    if(mRenderer)
    {
        CLBRenderer *lRenderer=mRenderer;
        mRenderer=nullptr;
        delete lRenderer;
    }
    switch(GetStyle()&STYLE_RENDERER)
    {
    case STYLE_ARRAY:
        mRenderer=new CLBArrayRenderer(this);
        break;
    case STYLE_LIST:
        mRenderer=new CLBListRenderer(this);
        break;
    case STYLE_DETAIL:
        mRenderer=new CLBDetailRenderer(this);
        break;
    case STYLE_CHECKBOXES:
        mRenderer=new CLBCheckRenderer(this);
        break;
    case STYLE_GROUPTREE:
        mRenderer=new CLBGroupRenderer(this);
        break;
    case STYLE_STRIP:
        mRenderer=new CLBStripRenderer(this);
        break;
    default:
        throw XInvalid(I18N("Wrong renderer in CListBox::CreateRenderer"));
    }
    mRenderer->OnBigChange();
}

void CListBox::AdjustHeight()
{
    if(mRenderer)
    {
        double lSize=mRenderer->GetTotalHeight();
        if(lSize<Size().y)
            Resize(CSize_d(Size().x,(lSize<5)?5:lSize));
    }
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/////////////////////// Event handlers ////////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CListBox::OnExpose(const CExposeEvent &pE)
{
    if(!mRenderer)
        CreateRenderer();

    mRenderer->OnExpose(pE);

    get_default_theme()->DrawShadow(pE.mGC,CRect_d({0,0},Size()));
}

void CListBox::OnHScroll(int pPos)
{
//     int lScrollBy=(int)mScrollPos.x-pPos;
     mScrollPos.x=pPos;
     Refresh();
//     if(lScrollBy)
//         ScrollBy(CPoint_d(lScrollBy,0));
//     mRenderer->OnXScrollChanged((int)mScrollPos.x);
}

void CListBox::OnVScroll(int pPos)
{
    ///\todo
//     int lScrollBy=(int)mScrollPos.y-pPos;
     mScrollPos.y=pPos;
     Refresh();
//     if(lScrollBy)
//         ScrollBy(CPoint_d(0,lScrollBy));
}

void CListBox::OnButtonDown(const CMouseEvent &pE)
{
    if(mEditing)
    {
        EndEditing();
    }

    if(pE.mButton==EMouseButton::LEFT)
    {
        if(pE.mClickCount>1)
        {
            if(pE.mClickCount==2)
            {
                mDragging=false;
                mSelecting=false;
                SItem *lItem=mRenderer->GetItemAt(pE.mPoint);

                if(lItem)
                {
                    OnItemDblClk(lItem);
                }
            }
            return;
        }

        int lType;
        SItem *lItem=mRenderer->GetItemAt(pE.mPoint,&lType);

        if(lItem)
        {
            if(GetStyle(STYLE_ONECLICK))
                eDblClick(lItem,lItem->mID);
            if((!lItem->mSelected)||mLWSelected>1||(!GetStyle(STYLE_EDITABLE)))
            {
                if(((pE.mMods&KEYMOD_CONTROL)||GetStyle(STYLE_TOGGLE))&&
                                    GetStyle(STYLE_MULTIPLE))
                    ToggleItem(lItem);
                else
                    SelectItem(lItem);
            }
            else                                //Item already selected
            {
                if(lType&LBHT_TEXT)
                {
                    if(GetStyle(STYLE_EDITABLE))
                    {
                        StartEditing(lItem);
                        return;
                    }
                }
            }

            mDragging=true;
        }
        else
        {
            UnselectAll();
            if(GetStyle(STYLE_MULTIPLE))
                mSelecting=true;
        }

        mDragPoint=pE.mPoint;
        mLastDragPoint=pE.mPoint;
    }
    else if(pE.mButton==EMouseButton::RIGHT)
    {
        SItem *lItem=mRenderer->GetItemAt(pE.mPoint);

        if(lItem&&!lItem->mSelected)
        {
            SelectItem(lItem);
        }

        if(GetStyle(STYLE_MENU))
        {
            PMenu lMenu=new CMenu;

            bool lItemMenu=lItem?true:false;

            if(!lItemMenu)
            {
                if(GetStyle(STYLE_ICONMENU))
                {
                    PMenu lSubMenu=lMenu->AddSubMenu(L"icon size");
                    lSubMenu->Add(new_menu_item(L"small"))->
                    eTriggered.Connect(&CListBox::SetIconSize,this,CIcon::MEDIUM);
                    lSubMenu->Add(new_menu_item(L"medium"))->
                    eTriggered.Connect(&CListBox::SetIconSize,this,CIcon::LARGE);
                    lSubMenu->Add(new_menu_item(L"large"))->
                    eTriggered.Connect(&CListBox::SetIconSize,this,CIcon::LARGER);
                    lSubMenu->Add(new_menu_item(L"huge"))->
                    eTriggered.Connect(&CListBox::SetIconSize,this,CIcon::LARGEST);
                }

                if(GetStyle(STYLE_VIEWMENU))
                {
                    PMenu lSubMenu=lMenu->AddSubMenu(L"view style");
                    lSubMenu->Add(new_menu_item(L"array"))->
                    eTriggered.Connect((void(CListBox::*)(TStyle,TStyle))&CListBox::SetStyle,this,(int)STYLE_VIEW,(int)STYLE_ARRAYVIEW);
                    lSubMenu->Add(new_menu_item(L"list"))->
                    eTriggered.Connect((void(CListBox::*)(TStyle,TStyle))&CListBox::SetStyle,this,(int)STYLE_VIEW,(int)STYLE_LISTVIEW);
                    lSubMenu->Add(new_menu_item(L"detail"))->
                    eTriggered.Connect((void(CListBox::*)(TStyle,TStyle))&CListBox::SetStyle,this,(int)STYLE_VIEW,(int)STYLE_DETAILVIEW);
                }

                if((GetStyle(STYLE_VIEWMENU|STYLE_ICONMENU))&&GetStyle(STYLE_USERMENU))
                    lMenu->AddSeparator();
            }

            OnFillMenu(lMenu,lItemMenu);

            if(GetStyle(STYLE_USERMENU))
                sFillMenu(lMenu,lItemMenu);

            if(lMenu->size())
            {
                PMenuBar lMenuBar=new CMenuBar;
                ///\todo lMenuBar->CreatePopup(this,lMenu,pE->mRootPoint);
                lMenuBar->Map();
            }
        }
    }
}

void CListBox::OnButtonUp(const CMouseEvent &pE)
{
    if(pE.mButton==EMouseButton::LEFT)
    {
        if(mDragging)
        {
            mDragging=false;
        }
        else if(mSelecting)
        {
            mSelecting=false;

            if(mLastDragPoint!=mDragPoint)
            {
                Refresh(CRect_d(mDragPoint,mLastDragPoint));
            }
        }
    }
}

void CListBox::OnMouseMove(const CMouseEvent &pE)
{
    if(mDragging&&GetStyle(STYLE_DRAGNDROP))
    {
        if(dist(mDragPoint,pE.mPoint)<10) return;

        SItem *lItem=GetSelItem();
        CDrag *lDrag=new CDrag(this,lItem->mImage?:
                            lItem->mIcon->GetPixmap(mIconSize));
        lDrag->SetHotSpot(mDragPoint-mRenderer->GetIconPos(lItem));
        lDrag->Drag();
        mDragging=false;
    }
    else if(mSelecting)
    {
        CRect_d lOldRect(mDragPoint,mLastDragPoint);
        CRect_d lNewRect(mDragPoint,pE.mPoint);

        SItem *lItems[GetItemCount()];
        int lNumItems=mRenderer->GetItemsIn(lNewRect,lItems);

        SelectItems(lNumItems,lItems);

        if(mLastDragPoint!=mDragPoint)
        {
            CRegion lRemovedRegion(lOldRect);
            lRemovedRegion-=lNewRect;

            Refresh(lRemovedRegion,true);
        }

        mLastDragPoint=pE.mPoint;

        if(mLastDragPoint!=mDragPoint)
        {
            ///\todo this again
            img::CGC *lGC=StartPainting(CRegion(lNewRect));
            lGC->SetSource(img::CColor(0.8,0.6,0.25,0.4));

            CRegion lNewRegion(lNewRect);
            lNewRegion-=lOldRect;

            lGC->Path().Rect(lNewRect);
            lGC->FillPath();
            EndPainting(lGC,CRegion(lNewRect));
        }
    }
}

void CListBox::OnMouseWheel(const CMouseEvent &pE)
{
    if(pE.mScrollX!=0.0)
    {
        mHScroll->Scroll(pE.mScrollX*mHScroll->GetSmallStep());
    }
    if(pE.mScrollY!=0.0)
    {
        mVScroll->Scroll(pE.mScrollY*mVScroll->GetSmallStep());
    }
}

void CListBox::OnSize(const CGeometryEvent &pE)
{
    if(mRenderer)
        mRenderer->OnSize(pE);
}

void CListBox::OnFocusIn(const CFocusEvent &pE)
{
    Refresh();
}

void CListBox::OnFocusOut(const CFocusEvent &pE)
{
    Refresh();
}

void CListBox::OnStyleChanged(TStyle pOld)
{
    if(!Mapped()) return;
    
    TStyle lChange=pOld^GetStyle();

    if(lChange&STYLE_FAKE)
    {
        if(GetStyle(STYLE_FAKE))
            SetInputMode(EInputMode::MOUSETRANSPARENT);
        else
            SetInputMode(EInputMode::NORMAL);
    }
    if(lChange&STYLE_RENDERER)
    {
        CreateRenderer();
    }
    if(lChange&STYLE_VIEW)
    {
        Refresh();
    }
}

void CListBox::OnItemSelected(SItem *pItem)
{
    if(mRenderer)
        mRenderer->OnItemSelected(pItem);

    if(pItem->mSelected)
        sSelected(pItem,pItem->mID);
    else
        sUnselected(pItem,pItem->mID);
}

void CListBox::OnItemDblClk(SItem *pItem)
{
    eDblClick(pItem,pItem->mID);
}

void CListBox::OnDragDrag(CDrag*,CClipboard *pCB)
{
    pCB->SetString(GetSelItem()->mText);
}

void CListBox::OnDropEnter(CDrop *pDrop,const CPoint_d&)
{
    pDrop->Accept();
}

void CListBox::OnDropDrop(CDrop *pDrop,const CPoint_d &pPoint,EDNDActions /*pAction*/)
{
    io::CURI lURI;
    get_toolkit()->GetClipboard(CClipboard::DRAGNDROP)->GetURI(lURI);
    sDrop(pDrop,pPoint);
}

void CListBox::OnItemChanged(SItem *pItem)
{
    if(mRenderer&&pItem->mRendererData) //TODO:check why it wouldn't possibly exist
        mRenderer->OnItemChanged(pItem);
}

void CListBox::OnLWFieldsChanged()
{
    if(mRenderer)
        mRenderer->OnFieldsChanged();
}

void CListBox::OnLWBigChange()
{
    if(mRenderer)
        mRenderer->OnBigChange();
}

void CListBox::OnItemAdded(SItem *pItem)
{
    if(mRenderer)
        mRenderer->OnItemAdded(pItem);
}

void CListBox::OnItemRemoved(SItem *pItem)
{
    if(mRenderer)
        mRenderer->OnItemRemoved(pItem);
}


/*namespace gui*/ } /*namespace buola*/ }
